package com.ds.lens.data.service;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Lists;
import com.ds.lens.data.common.asserts.Asserts;
import com.ds.lens.data.common.constant.Constants;
import com.ds.lens.data.common.constant.MonitorStateEnum;
import com.ds.lens.data.common.document.MetricState;
import com.ds.lens.data.service.dao.LensMonitorMuteInfoDao;
import com.ds.lens.data.service.dao.LensMonitorSnapshotDao;
import com.ds.lens.data.service.dao.LensMonitorStateInfoDao;
import com.ds.lens.data.service.dao.LensMonitorStateMainDao;
import com.ds.lens.data.service.dao.entity.LensMonitor;
import com.ds.lens.data.service.dao.entity.LensMonitorMuteInfo;
import com.ds.lens.data.service.dao.entity.LensMonitorStateInfo;
import com.ds.lens.data.service.dao.entity.LensMonitorStateMain;
import com.ds.lens.data.vo.in.PagedConditionInVO;
import com.ds.lens.data.vo.in.User;
import com.ds.lens.data.vo.in.v2.monitor.state.LensMonitorStateInVO;
import com.ds.lens.data.vo.in.v2.monitor.state.LensMonitorStateSaveInVO;
import com.ds.lens.data.vo.in.v2.monitor.state.LensMonitorStateUpdateVO;
import com.ds.lens.data.vo.out.PageResult;
import com.ds.lens.data.vo.out.v2.event.GroupStatusListOutVO;
import com.ds.lens.data.vo.out.v2.event.GroupStatusOutVO;
import com.ds.lens.data.vo.out.v2.event.MonitorMuteOutVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author WeiShaoying
 * @date 2019/11/14 上午11:24
 */
@Service
@Slf4j
public class LensMonitorStateService extends BaseService {

    @Autowired
    LensMonitorStateInfoDao lensMonitorStateInfoDao;

    @Autowired
    LensMonitorStateMainDao lensMonitorStateMainDao;

    @Autowired
    LensMonitorMuteInfoDao lensMonitorMuteInfoDao;

    @Autowired
    LensMonitorSnapshotDao lensMonitorSnapshotDao;

    @Autowired
    LensMonitorService lensMonitorService;

    public PageResult<GroupStatusListOutVO> pageMonitorStateRecords(PagedConditionInVO<LensMonitorStateInVO> conditionInVO, User user) {
        LensMonitorStateInVO inVO = conditionInVO.getCondition();
        Asserts.assertNotNull(inVO, Constants.ErrorCode.PARAM_ERROR, "Condition can not be null.");
        Long monitorId = inVO.getMonitorId();
        Integer version = inVO.getVersion();
        Asserts.assertNotNull(monitorId, Constants.ErrorCode.PARAM_ERROR, "MonitorId can not be null.");
        Asserts.assertNotNull(version, Constants.ErrorCode.PARAM_ERROR, "Version can not be null.");

        LensMonitor lensMonitor = getLensMonitor(monitorId, version);
        checkUserMonitors(Collections.singletonList(lensMonitor), user);
        Asserts.assertNotNull(lensMonitor, Constants.ErrorCode.DATA_NOT_EXIST);
        PageHelper.startPage(conditionInVO.getPage().getIndex(), conditionInVO.getPage().getSize());
        Date startDate = new Date(inVO.getStartTime());
        Date endDate = new Date(inVO.getEndTime());
        //分页查询target
        Page<LensMonitorStateMain> page = lensMonitorStateMainDao.selectTargetsByPage(monitorId, version, inVO.getTarget(), getMonitorStates(inVO));
        List<GroupStatusListOutVO> result;
        if (null != page && CollectionUtils.isNotEmpty(page.getResult())) {
            List<LensMonitorStateMain> targets = page.getResult();
            result = Lists.newArrayListWithCapacity(targets.size());
            //根据target获取相应的Monitor States
            for (LensMonitorStateMain target : targets) {
                getStates(inVO, startDate, endDate, result, target, lensMonitor);
            }
            return PageResult.of(result, page.getTotal(), page.getPageNum(), page.getPageSize());
        } else {
            return getNoDataResult(inVO, lensMonitor);
        }
    }

    private LensMonitor getLensMonitor(Long monitorId, Integer version) {
        LensMonitor lensMonitor = lensMonitorSnapshotDao.selectSnapshoot(monitorId, version);
        if (lensMonitor != null) {
            return lensMonitor;
        }
        return lensMonitorDao.selectByPrimaryKey(monitorId);
    }

    private PageResult<GroupStatusListOutVO> getNoDataResult(LensMonitorStateInVO inVO, LensMonitor lensMonitor) {
        List<GroupStatusListOutVO> result;
        result = Lists.newArrayListWithCapacity(1);
        GroupStatusListOutVO outVO = new GroupStatusListOutVO();
        outVO.setTarget("*");
        GroupStatusOutVO vo = new GroupStatusOutVO();
        vo.setStatus(MonitorStateEnum.NO_DATA.getState());
        if (inVO.getStartTime() < lensMonitor.getVersionTime().getTime()) {
            vo.setTimestamp(lensMonitor.getVersionTime().getTime());
        } else {
            vo.setTimestamp(inVO.getStartTime());
        }
        outVO.setGroupStatusOutVOS(Collections.singletonList(vo));
        outVO.setMuteOutVOS(Collections.singletonList(new MonitorMuteOutVO(vo.getTimestamp(), lensMonitor.getMute())));
        result.add(outVO);
        return PageResult.of(result, 1L);
    }

    private List<Integer> getMonitorStates(LensMonitorStateInVO inVO) {
        List<Integer> states = Lists.newArrayListWithCapacity(MonitorStateEnum.values().length);
        if (CollectionUtils.isNotEmpty(inVO.getStatus())) {
            inVO.getStatus().forEach(state -> states.add(state.getState()));
        }
        return states;
    }

    private void getStates(LensMonitorStateInVO inVO, Date startDate, Date endDate, List<GroupStatusListOutVO> result, LensMonitorStateMain target, LensMonitor lensMonitor) {
        GroupStatusListOutVO listOutVO = new GroupStatusListOutVO();
        List<GroupStatusOutVO> groupStatusOutVOS;
        List<MonitorMuteOutVO> muteOutVOS;
        listOutVO.setTarget(target.getTarget());
        //获取state集合
        groupStatusOutVOS = getGroupStatusOutVOS(inVO, startDate, endDate, target, lensMonitor);
        //获取mute事件状态集合
        muteOutVOS = getMuteOutVOS(inVO, startDate, endDate, lensMonitor);

        listOutVO.setGroupStatusOutVOS(groupStatusOutVOS);
        listOutVO.setMuteOutVOS(muteOutVOS);
        result.add(listOutVO);
    }

    private List<MonitorMuteOutVO> getMuteOutVOS(LensMonitorStateInVO inVO, Date startDate, Date endDate, LensMonitor lensMonitor) {
        List<MonitorMuteOutVO> muteOutVOS;
        List<LensMonitorMuteInfo> muteInfos = lensMonitorMuteInfoDao.selectListByMonitor(inVO.getMonitorId(), startDate, endDate);
        if (CollectionUtils.isNotEmpty(muteInfos)) {
            muteOutVOS = Lists.newArrayListWithCapacity(muteInfos.size());
            LensMonitorMuteInfo muteInfo = muteInfos.get(0);
            if (muteInfo.getCreatedTime().after(startDate)) {
                muteOutVOS.add(new MonitorMuteOutVO(inVO.getStartTime(), !muteInfo.getMute()));
            }
            for (LensMonitorMuteInfo info : muteInfos) {
                muteOutVOS.add(new MonitorMuteOutVO(info.getCreatedTime().getTime(), info.getMute()));
            }
        } else {
            muteOutVOS = Collections.singletonList(new MonitorMuteOutVO(inVO.getStartTime(), lensMonitor.getMute()));
        }
        return muteOutVOS;
    }

    private List<GroupStatusOutVO> getGroupStatusOutVOS(LensMonitorStateInVO inVO, Date startDate, Date endDate, LensMonitorStateMain target, LensMonitor lensMonitor) {
        List<GroupStatusOutVO> groupStatusOutVOS;
        List<LensMonitorStateInfo> list = lensMonitorStateInfoDao.selectByMonitor(inVO.getMonitorId(), inVO.getVersion(), target.getTarget(), null, startDate, endDate);
        if (CollectionUtils.isNotEmpty(list)) {
            LensMonitorStateInfo stateHistory = list.get(0);
            groupStatusOutVOS = Lists.newArrayListWithCapacity(list.size());
            if (stateHistory.getCreatedTime().after(startDate)) {
                groupStatusOutVOS.add(new GroupStatusOutVO(lensMonitor.getVersionTime().getTime(), stateHistory.getPreState() == null ?
                        MonitorStateEnum.NO_DATA.getState() : stateHistory.getPreState()));
            }
            for (LensMonitorStateInfo monitorStateHistory : list) {
                groupStatusOutVOS.add(new GroupStatusOutVO(monitorStateHistory.getCreatedTime().getTime(), monitorStateHistory.getState()));
            }
        } else {
            GroupStatusOutVO outVO = new GroupStatusOutVO();
            if (lensMonitor.getVersionTime().after(startDate)) {
                outVO.setTimestamp(lensMonitor.getVersionTime().getTime());
            } else {
                outVO.setTimestamp(inVO.getStartTime());
            }
            outVO.setStatus(target.getState() == null ? MonitorStateEnum.NO_DATA.getState() : target.getState());
            groupStatusOutVOS = Collections.singletonList(outVO);
        }
        return groupStatusOutVOS;
    }

    @Transactional
    public Map<String, MetricState> saveMonitorState(LensMonitorStateSaveInVO saveInVO) {
        log.info("saveMonitorState start:{}", JSON.toJSONString(saveInVO));
        List<LensMonitorStateMain> stateMains = lensMonitorStateMainDao.selectByMonitor(saveInVO.getMonitorId(), saveInVO.getVersion());
        Integer monitorState = lensMonitorDao.selectStateByIdVersion(saveInVO.getMonitorId(), saveInVO.getVersion());
        LensMonitorStateUpdateVO stateUpdateVO = new LensMonitorStateUpdateVO();
        stateUpdateVO.setId(saveInVO.getMonitorId());
        stateUpdateVO.setVersion(saveInVO.getVersion());
        stateUpdateVO.setLevel(MonitorStateEnum.NO_DATA.getLevel());
        if (monitorState != null) {
            stateUpdateVO.setPreLevel(MonitorStateEnum.valueOf(monitorState).getLevel());
        }
        Date createdTime = new Date(saveInVO.getModifiedTime());
        Map<String, MetricState> result = new HashMap<>();
        List<LensMonitorStateInfo> batchStateInfos = new ArrayList<>(saveInVO.getTargets().size());
        List<LensMonitorStateMain> batchStateMains = new ArrayList<>(saveInVO.getTargets().size());
        long renotifyTime = createdTime.getTime() - saveInVO.getRenotifyDuration() * 60000;
        long missingTime = createdTime.getTime() - 2 * saveInVO.getInterval() * 60000;
        if (!CollectionUtils.isEmpty(stateMains)) {
            stateMains.forEach(stateMain -> {
                Integer state = saveInVO.getTargets().remove(stateMain.getTarget());
                if (state != null) {
                    if (state.compareTo(stateMain.getState()) == 0
                            && state != MonitorStateEnum.OK.getState()
                            && saveInVO.getRenotifyDuration() > 0
                            && renotifyTime >= stateMain.getLastTime().getTime()) {
                        lensMonitorStateMainDao.updateById(stateMain.getId(), state, createdTime);
                        setResult(result, saveInVO.getMute(), stateMain.getTarget(), state, true);
                    } else if (state.compareTo(stateMain.getState()) != 0) {
                        lensMonitorStateMainDao.updateById(stateMain.getId(), state, createdTime);
                        batchStateInfos.add(getStateInfo(stateMain.getMonitorId(), stateMain.getVersion(), stateMain.getTarget(),
                                state, stateMain.getState(), createdTime));
                        setResult(result, saveInVO.getMute(), stateMain.getTarget(), state, false);
                    }
                    setMonitorMaxState(stateUpdateVO, state);
                } else {
                    if (saveInVO.getMissing()
                            && stateMain.getState() != MonitorStateEnum.NO_DATA.getState()
                            && missingTime >= stateMain.getLastTime().getTime()) {
                        lensMonitorStateMainDao.updateById(stateMain.getId(), MonitorStateEnum.NO_DATA.getState(), createdTime);
                        batchStateInfos.add(getStateInfo(stateMain.getMonitorId(), stateMain.getVersion(), stateMain.getTarget(),
                                MonitorStateEnum.NO_DATA.getState(), stateMain.getState(), createdTime));
                        setResult(result, saveInVO.getMute(), stateMain.getTarget(), MonitorStateEnum.NO_DATA.getState(), false);
                        stateMain.setState(MonitorStateEnum.NO_DATA.getState());
                    } else if (stateMain.getState() == MonitorStateEnum.NO_DATA.getState()
                            && saveInVO.getRenotifyDuration() > 0
                            && renotifyTime >= stateMain.getLastTime().getTime()) {
                        lensMonitorStateMainDao.updateById(stateMain.getId(), MonitorStateEnum.NO_DATA.getState(), createdTime);
                        setResult(result, saveInVO.getMute(), stateMain.getTarget(), MonitorStateEnum.NO_DATA.getState(), true);
                    }
                    setMonitorMaxState(stateUpdateVO, stateMain.getState());
                }
            });
        }
        saveInVO.getTargets().forEach((s, i) -> {
            batchStateMains.add(getStateMain(saveInVO.getMonitorId(), saveInVO.getVersion(), s, i, createdTime));
            batchStateInfos.add(getStateInfo(saveInVO.getMonitorId(), saveInVO.getVersion(), s, i,
                    MonitorStateEnum.NO_DATA.getState(), createdTime));
            setResult(result, saveInVO.getMute(), s, i, false);
            setMonitorMaxState(stateUpdateVO, i);
        });
        if (CollectionUtils.isNotEmpty(batchStateMains)) {
            lensMonitorStateMainDao.batchInsert(batchStateMains);
        }
        if (CollectionUtils.isNotEmpty(batchStateInfos)) {
            lensMonitorStateInfoDao.batchInsert(batchStateInfos);
        }
        if (stateUpdateVO.getPreLevel() != null && stateUpdateVO.getLevel().compareTo(stateUpdateVO.getPreLevel()) != 0) {
            lensMonitorDao.updateStateByIdVersion(stateUpdateVO);
        }
        log.info("saveMonitorState end:{}", JSON.toJSONString(result));
        return result;
    }

    private LensMonitorStateInfo getStateInfo(Long monitorId, Integer version, String target,
                                              Integer state, Integer preState, Date createdTime) {
        LensMonitorStateInfo stateInfo = new LensMonitorStateInfo();
        stateInfo.setMonitorId(monitorId);
        stateInfo.setVersion(version);
        stateInfo.setTarget(target);
        stateInfo.setState(state);
        stateInfo.setPreState(preState);
        stateInfo.setCreatedTime(createdTime);
        return stateInfo;
    }

    private LensMonitorStateMain getStateMain(Long monitorId, Integer version, String target, Integer state, Date createdTime) {
        LensMonitorStateMain stateMain = new LensMonitorStateMain();
        stateMain.setMonitorId(monitorId);
        stateMain.setVersion(version);
        stateMain.setTarget(target);
        stateMain.setState(state);
        stateMain.setLastTime(createdTime);
        return stateMain;
    }

    private void setMonitorMaxState(LensMonitorStateUpdateVO stateUpdateVO, Integer state) {
        MonitorStateEnum stateEnum = MonitorStateEnum.valueOf(state);
        if (stateEnum.getLevel() > stateUpdateVO.getLevel()) {
            stateUpdateVO.setLevel(stateEnum.getLevel());
        }
    }

    private void setResult(Map<String, MetricState> result, Boolean mute, String target, Integer state, Boolean renotify) {
        if (!mute) {
            MetricState metricState = new MetricState();
            metricState.setState(state);
            metricState.setRenotify(renotify);
            result.put(target, metricState);
        }
    }

    @Transactional
    public void artificialRestorationMonitorState(Long monitorId, Integer version) {
        List<LensMonitorStateMain> lensMonitorStateMains = lensMonitorStateMainDao.selectByMonitor(monitorId, version);
        Date createdTime = new Date();
        if (!CollectionUtils.isEmpty(lensMonitorStateMains)) {
            List<LensMonitorStateInfo> lensMonitorStateInfos = new ArrayList<>();
            lensMonitorStateMains.forEach(lensMonitorStateMain -> {
                if (lensMonitorStateMain.getState() != MonitorStateEnum.OK.getState()) {
                    LensMonitorStateInfo lensMonitorStateInfo = new LensMonitorStateInfo();
                    BeanUtils.copyProperties(lensMonitorStateMain, lensMonitorStateInfo);
                    lensMonitorStateInfo.setState(MonitorStateEnum.OK.getState());
                    lensMonitorStateInfo.setPreState(lensMonitorStateMain.getState());
                    lensMonitorStateInfo.setCreatedTime(createdTime);
                    lensMonitorStateInfos.add(lensMonitorStateInfo);
                }
            });
            lensMonitorStateMainDao.updateAllByMonitor(monitorId, version, MonitorStateEnum.OK.getState(), createdTime);
            lensMonitorStateInfoDao.batchInsert(lensMonitorStateInfos);
        }
    }
}
